package luj.tool.jnproxyan.proxy.util.data;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import luj.bean.api.BeanContext;
import luj.data.api.Data;
import luj.data.api.type.DataType;
import luj.data.api.use.DataUse;
import luj.data.api.use.DirtyItem;
import luj.data.api.use.UseGroup;
import luj.data.internal.data.types.map.MapInternal;
import luj.tool.jnproxyan.proxy.database.DatabaseState;
import luj.tool.jnproxyan.proxy.util.data.session.DataUsingStarter;
import luj.tool.jnproxyan.proxy.util.data.wrap.ResultProxy;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.JSON;
import org.jooq.JSONValueOnStep;
import org.jooq.Query;
import org.jooq.Record;
import org.jooq.SQL;
import org.jooq.Table;
import org.jooq.impl.DSL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.name;

@Component
public class DataDirtySaver {

  public static final Table<Record> T_DATA = DSL.table(name("data"));
  public static final Table<Record> T_DATAN = DSL.table(name("data_n"));

  public static final Field<Long> F_ID = field(name("id"), Long.class);
  public static final Field<String> F_TABLE = field(name("table"), String.class);
  public static final Field<String> F_DATA = field(name("data"), String.class);
  public static final Field<Long> F_PARENT = field(name("parentId"), Long.class);

  public DataDirtySaver inject(DataUsingStarter.Session s) {
    return inject(s.db(), s.using());
  }

  public DataDirtySaver inject(DatabaseState dbState, UseGroup group) {
    DataDirtySaver result = new DataDirtySaver();
    result._dbState = dbState;
    result._jooq = dbState.getJooq();
    result._group = group;

    result._lujbean = _lujbean;
    return result;
  }

  public void save() {
    LOG.debug("准备save，下面输出不应该为空");

    List<DirtyItem> dirtyList = _group.stream()
        .map(DataUse::toDirty)
        .collect(Collectors.toList());

    _jooq.batch(dirtyList.stream()
        .flatMap(i -> makeSqlCmd(i).stream())
        .collect(Collectors.toList())).execute();

    for (DirtyItem item : dirtyList) {
      item.flush();
    }
  }

  private List<Query> makeSqlCmd(DirtyItem item) {
//      Data data = item.getData();
    DataType type = item.getDataType();

    String table = type.getName();//.replaceFirst("luj\\.tool\\.jnproxyan\\.proxy\\.", "");
    LOG.debug("insert {} {} where id={}", table, item.getPrimitiveFields(), item.getDataId());

    return ImmutableList.<Query>builder()
        .addAll(savePrimitive(item, _jooq))
        .addAll(saveMapFields(item))
        .build();
  }

  private List<Query> savePrimitive(DirtyItem item, DSLContext jooq) {
    DataType type = item.getDataType();
    String table = type.getName();//.replaceFirst("luj\\.tool\\.jnproxyan\\.proxy\\.", "");

    Long dataId = (Long) item.getDataId();
    Map<String, Object> primitiveDirty = item.getPrimitiveFields();
    LOG.debug("primitiveDirty:{}, id:{}", primitiveDirty, dataId);

    if (item.isNew()) {
      return ImmutableList.of(jooq.insertInto(T_DATA, F_ID, F_TABLE, F_DATA)
          .values(dataId, table, toJsonStr(primitiveDirty)));
    }

    if (primitiveDirty.isEmpty()) {
      return ImmutableList.of();
    }

    String dirtyJson = toJsonStr(primitiveDirty);
    SQL jsonPatch = DSL.sql("json_patch({0}, {1})", F_DATA, dirtyJson);

    return ImmutableList.of(jooq.update(T_DATA)
        .set(F_DATA, DSL.select(field(jsonPatch, String.class)).from(T_DATA).where(F_ID.eq(dataId)))
        .where(F_ID.eq(dataId))
    );
  }

  private List<Query> saveMapFields(DirtyItem item) {
    DataType type = item.getDataType();

//    for (Map.Entry<String, MapInternal.Dirty> e : item.getMapFields().entrySet()) {
//      String field = e.getKey();
//      String table = type.getName() + "#" + field;
//
//      MapInternal.Dirty changed = e.getValue();
//
//      LOG.debug("insert {} {} where parentId={}", table, changed.updated().entrySet().stream()
//          .collect(toMap(p -> p.getKey(), p -> _lujbean
//              .getBeanFrom(p.getValue()).asCustom().getCustom(ResultProxy.class).getData().getId())), item.getDataId());
//    }

    return item.getMapFields().entrySet().stream()
        .flatMap(e -> saveMapSingle(item, e.getKey(), e.getValue()))
        .collect(Collectors.toList());
  }

  private Stream<Query> saveMapSingle(DirtyItem item, String field, MapInternal.Dirty dirty) {
    DataType type = item.getDataType();
    String table = type.getName() + "#" + field;

    Long dataId = (Long) item.getDataId();
    return saveMapUpdated(table, dirty.updated(), dataId);
  }

  private Stream<Query> saveMapUpdated(String table, Map<Object, Object> value, Long parent) {
//    Field<? > keyField = DSL.jsonValue(T_DATAN.field("d", JSON.class), "$.key");
    Field<Object> keyField = field("json_extract(" + F_DATA.getName() + ",'$.key')");
    //TODO: 不确定实现好没有

    return value.entrySet().stream().flatMap(e -> Stream.of(
//        _jooq.deleteFrom(T_DATAN).where(F_TABLE.eq(table)).and(F_PARENT.eq(parent)).and(keyField.eq(e.getKey())),
        _jooq.insertInto(T_DATAN, F_TABLE, F_DATA, F_PARENT).values(table, toEntryStr(e), parent)
    ));
  }

  private String toEntryStr(Map.Entry<Object, Object> e) {
    Object val = resolveValue(e.getValue());
    return toJsonStr(ImmutableMap.of("key", e.getKey(), "value", val));
  }

  private Object resolveValue(Object val) {
    if (Proxy.isProxyClass(val.getClass())) {
      return _lujbean.getBeanFrom(val).asCustom().getCustom(ResultProxy.class).getData().getId();
    }
    return val;
  }

  private String toJsonStr(Map<String, Object> fields) {
    try {
      return JACKSON.writeValueAsString(fields);
    } catch (JsonProcessingException e) {
      throw new UnsupportedOperationException(e);
    }
  }

  private static final Logger LOG = LoggerFactory.getLogger(DataDirtySaver.class);
  private static final ObjectMapper JACKSON = new ObjectMapper();

  DatabaseState _dbState;
  DSLContext _jooq;

  UseGroup _group;

  @Autowired
  BeanContext _lujbean;
}
